
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author David Beaver
 */
public class FtpHandler implements Runnable{
    private InputStream input;
    private String realPath;
    private String command;
    private OutputStream output;
    private BufferedReader headerReader;
    private HashMap<String,String> headers;
    private ServerConfig config;
    private Socket ftpSocket;
    
    public FtpHandler(Socket incoming, ServerConfig config){
        this.realPath = "";
        this.ftpSocket = incoming;
        this.command = "";
        try {
            this.config = config;
            this.input = this.ftpSocket.getInputStream();
            this.output = this.ftpSocket.getOutputStream();
            this.headerReader = new BufferedReader(new InputStreamReader(this.ftpSocket.getInputStream()));
        } catch (IOException ex) {
            Logger.getLogger(FtpHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.headers = new HashMap<String,String>();
    }
    
    public void run() {
        try{
            System.out.println("FTP: Processing request...");
            if(this.headerReader.ready()){ //if there are headers to read
                command = this.headerReader.readLine();  //save the command
                System.out.println("FTP:\tCommand: "+this.command);
                String relativePath = ""; 
                if(this.headerReader.ready()){
                    relativePath = this.headerReader.readLine(); //save the relative path
                }
                StringTokenizer pathParser = new StringTokenizer(relativePath,"/"); //parse the path by directories
                while(pathParser.hasMoreTokens()){ //while there are more directories to check
                    String dir = pathParser.nextToken(); //read the next directory
                    if(this.config.isAlias(dir)){ //if its an alis
                        realPath += this.config.resolveAlias(dir); //resolve path for real path
                    }else{ //its not an alias
                        realPath += dir + File.separator; //add it on real path
                    }
                }
                System.out.println("FTP:\tPath: "+this.realPath);
                while(this.headerReader.ready()){ //while there are more headers
                    String newLine = this.headerReader.readLine(); //read the next line
                    System.out.println("FTP:\t"+newLine);
                    StringTokenizer headerParser = new StringTokenizer(newLine); //parse the line
                    if(headerParser.hasMoreTokens()){ //if there are headers
                        String header = headerParser.nextToken(); //save the header
                        String tag = headerParser.nextToken(); //save its tagged value
                        this.headers.put(header, tag); //save the mapping
                    }
                }
                System.out.println();
            }
        }catch(IOException ex){
            System.out.println("FTP: Error processing request");
        }
        if(command.equals("upload")) //if the command is upload
            this.upload(); //handle upload request
        else if(command.equals("download")) //if the command is download
            this.download(); //handle download request
    }
    
    private void upload(){
        try{
            System.out.print("FTP: Checking meta-data...");
            /* the File-Name header must be included in order to upload */
            if(this.headers.containsKey("File-Size") && this.headers.containsKey("File-Name")){ //if the File-Name is included
                System.out.print("OK\n");
                byte[] buffer = new byte[new Integer(this.headers.get("File-Size"))]; //prepare our byte buffer
                System.out.print("FTP: Downloading "+realPath + this.headers.get("File-Name"));
                FileOutputStream out = new FileOutputStream(realPath + this.headers.get("File-Name")); //open new file
                String test = "OK\n";
                this.output.write("OK".getBytes());
                this.output.flush();
                int bytesRead = 0;
                int totalBytes = 0;
                while ((bytesRead = this.input.read(buffer)) > 0) { //while there are more bytes to read
                    System.out.print(".");
                    totalBytes += bytesRead; //add the number read to the total 
                    out.write(buffer); //write the bytes to the file
                }
                //done uploading and saving
                System.out.print("OK\n");
                System.out.println("FTP: "+totalBytes+"/"+this.headers.get("File-Size")+" bytes downloaded");
                this.output.write("OK".getBytes()); //respond OK
                out.flush(); //flush the file output stream
                out.close(); //close the file
            }else {
                System.out.print("Failed!\n");
                this.output.write("NO".getBytes());
            }
        }catch(IOException ex){
            System.out.println("Error downloading file");
        } finally {
            try {
                this.output.close(); //close socket output stream 
                this.input.close(); //close socket input stream
                this.ftpSocket.close(); //close the socket
            }
            catch (IOException e) {
                System.out.println("Error closing socket");
            }
        }      
    }
    
    private void download(){
        System.out.print("FTP: Checking if file exists...");
        File requestedFile = new File(this.realPath);
        if(requestedFile.exists()){
            
            System.out.print("OK\nFTP: The file contains:");
            if(requestedFile.isDirectory()){
                File[] files = requestedFile.listFiles();
                files = requestedFile.listFiles();
                String response = new String();
                
                for(File file : files){
                    response += "File-Name " + file.getName() + "\n";
                    response += "File-Size " + file.length() + "\n";
                }
                
                try {
                    response += "\n";
                    System.out.println(response+"\n");
                    System.out.print("Sending list of files to Server Manager...");
                    this.output.write(response.getBytes());
                    System.out.print("OK\n");
                    this.output.flush();
                } catch (IOException ex) {
                    System.out.println("FTP: Error sending response");
                }
            }else{
                InputStream is = null;
                try {
                    is = new FileInputStream(requestedFile); //open the file
                } catch (FileNotFoundException ex) {
                    System.out.println("FTP: File could not be found!");
                }
                // Get the size of the file
                long length = requestedFile.length(); //save the length of the file
                // Create the byte array to hold the data
                byte[] bytes = new byte[(int)length];
                // Read in the bytes
                int offset = 0;
                int numRead = 0;
                System.out.print("FTP: Preparing file");
                try {
                    while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length-offset)) >= 0) {
                        System.out.print(".");
                        offset += numRead;
                    }
                    System.out.print("DONE\n");
                    is.close();
                    System.out.print("FTP: Uploading file...");
                    this.output.write(bytes);
                    this.output.flush();
                    this.output.close();
                    this.ftpSocket.close();
                    System.out.print("DONE\n"+offset+" bytes read");
                } catch (IOException ex) {
                    System.out.println("FTP: Error uploading file");
                }
            }
            try {
                this.output.close();
            } catch (IOException ex) {
                Logger.getLogger(FtpHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

